#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>

#include "compat.h"
#include "psd_modules.h"
#include "psd_common.h"
#include "psd_event.h"
#include "psd_oscars_basic.h"
#include "psd_alarms.h"
#include "psd_logger.h"
#include "hashtable.h"

int psd_alarms_init();
psdAlarm *psd_alarm_alloc();
int psd_alarms_circuit_failure_handler(const char *event_id, void *arg, const char *description);
int psd_alarms_circuit_created_handler(const char *event_id, void *arg, const char *description);
int psd_alarms_connection_started_handler(const char *event_id, void *arg, const char *description);
int psd_alarms_connection_failed_handler(const char *event_id, void *arg, const char *description);
static psdAlarm *__psd_alarms_lookup_oscars_alarm(void *arg);
static void __psd_alarms_add_oscars_alarm(void *arg, const char *description);
static void psd_alarms_send_email(const char *type, const char *description, int alarm_fixed);

static LIST_HEAD(listhead, psd_alarm_t) alarm_list;

static struct hashtable *alarm_table = NULL;
static pthread_mutex_t alarm_table_lock = PTHREAD_MUTEX_INITIALIZER;

DEFINE_HASHTABLE_INSERT(alarm_table_insert, char, psdAlarm);
DEFINE_HASHTABLE_SEARCH(alarm_table_search, char, psdAlarm);
DEFINE_HASHTABLE_REMOVE(alarm_table_remove, char, psdAlarm);

int psd_alarms_do_send_email = 0;
char *psd_alarms_email_address = NULL;
char *psd_alarms_email_subject = NULL;
char *psd_alarms_sendmail_binary = NULL;

static psdModule psd_alarm_module = {
	.desc = "Daemon Alarming Module",
	.dependencies = "",
	.init = psd_alarms_init
};

psdModule *module_info() {
	return &psd_alarm_module;
}

int psd_alarms_init() {
	int i;
	int val;
	char *val_str;

	if (psd_depot_settings_get_bool("alarms", "send_email", &val) == 0) {
		psd_alarms_do_send_email = val;
	} else {
		psd_alarms_do_send_email = 1;
	}

	if (psd_alarms_do_send_email) {

		if (psd_depot_settings_get("alarms", "email_address", &val_str) != 0) {
			psd_err(0, "Must specify an email address to send alarms to (email_address)");
			goto error_exit;
		} else {
			psd_alarms_email_address = val_str;
		}

		if (psd_depot_settings_get("alarms", "sendmail_binary", &val_str) != 0) {
			psd_err(0, "Must specify the path to the sendmail binary (sendmail_binary)");
			goto error_exit;
		} else {
			psd_alarms_sendmail_binary = val_str;
		}

		psd_info(1, "Alarm messages will be sent to %s", psd_alarms_email_address);

		if (psd_depot_settings_get("alarms", "email_subject", &val_str) == 0) {
			psd_alarms_email_subject = val_str;
		}
	}

	alarm_table = create_hashtable(10, id_hash_fn, id_equal_fn);
	if (!alarm_table) {
		psd_err(0, "couldn't create alarm hash table");
		goto error_exit;
	}

	psd_event_register_listener("oscars.circuit.reserve.failure", psd_alarms_circuit_failure_handler);
	psd_event_register_listener("oscars.circuit.create.failure",  psd_alarms_circuit_failure_handler);
	psd_event_register_listener("oscars.circuit.allocated",       psd_alarms_circuit_created_handler);
	psd_event_register_listener("phoebus.connection.start",       psd_alarms_connection_started_handler);
	psd_event_register_listener("phoebus.connection.failed",      psd_alarms_connection_failed_handler);

	return 0;

error_exit:
	return -1;
}

static void psd_alarms_send_email(const char *type, const char *description, int alarm_fixed) {
	char body_buf[1024];
	char subj_buf[1024];
	char *status;

	if (alarm_fixed) {
		status = "FIXED";
	} else {
		status = "UNRESOLVED";
	}

	if (psd_alarms_email_subject) {
		sprintf(subj_buf, "%s (%s): %s", psd_alarms_email_subject, status, type);
	} else {
		sprintf(subj_buf, "Phoebus Alert (%s): %s", status, type);
	}

	if (alarm_fixed) {
		sprintf(body_buf, "Problem Detected By Phoebus Has Been Resolved\n%s", description);
	} else {
		sprintf(body_buf, "Problem Detected By Phoebus\n%s", description);
	}

	send_email(psd_alarms_sendmail_binary, psd_alarms_email_address, subj_buf, body_buf);
}

psdAlarm *psd_alarm_alloc() {
	psdAlarm *alarm;

	alarm = malloc(sizeof(*alarm));
	if (!alarm) {
		goto error_exit;
	}

	bzero(alarm, sizeof(*alarm));

	return alarm;

error_exit:
	return NULL;
}

void psd_alarm_free(psdAlarm *alarm) {
	if (alarm->id)
		free(alarm->id);
	if (alarm->description)
		free(alarm->description);
	free(alarm);
}

static psdAlarm *__psd_alarms_lookup_oscars_alarm(void *arg) {
	char buf[1024];
	psdPath *path = (psdPath *) arg;
	psdOSCARSPath *pi = (psdOSCARSPath *) path->path_private;
	char *src_id, *dst_id;
	psdAlarm *ret_alarm;

	src_id = pi->src;
	dst_id = pi->dst;

	if (strcmp(src_id, dst_id) > 0) {
		char *tmp = src_id;
		src_id = dst_id;
		dst_id = tmp;
	}

	snprintf(buf, sizeof(buf), "OSCARS:%s->%s@%s:%d", src_id, dst_id, pi->url, pi->vlan_id);

	return alarm_table_search(alarm_table, buf);
}

static void __psd_alarms_add_oscars_alarm(void *arg, const char *description) {
	char buf[1024];
	psdPath *path = (psdPath *) arg;
	psdOSCARSPath *pi = (psdOSCARSPath *) path->path_private;
	char *src_id, *dst_id;
	psdAlarm *alarm;

	psd_debug(5, "Adding oscars alarm: %s", description);

	src_id = pi->src;
	dst_id = pi->dst;

	if (strcmp(src_id, dst_id) > 0) {
		char *tmp = src_id;
		src_id = dst_id;
		dst_id = tmp;
	}

	snprintf(buf, sizeof(buf), "OSCARS:%s->%s@%s:%d", src_id, dst_id, pi->url, pi->vlan_id);

	alarm = psd_alarm_alloc();
	if (!alarm) {
		goto error_exit;
	}

	time(&alarm->ts);
	alarm->id = strdup(buf);
	if (!alarm->id) {
		goto error_exit_alarm;
	}
	alarm->description = strdup(description);

	if (alarm_table_insert(alarm_table, strdup(buf), alarm) == 0) {
		goto error_exit_alarm;
	}

	return;

error_exit_alarm:
	psd_alarm_free(alarm);
error_exit:
	return;
}

int psd_alarms_circuit_failure_handler(const char *event_id, void *arg, const char *description) {
	psd_debug(5, "Got circuit failure: %s", description);

	pthread_mutex_lock(&alarm_table_lock);
	{
		psdAlarm *alarm;

		alarm = __psd_alarms_lookup_oscars_alarm(arg);
		if (!alarm) {
			psd_debug(5, "Adding new alarm: %s", description);
			// this is the first time we've seen this link fail, so we need to generate an alarm
			__psd_alarms_add_oscars_alarm(arg, description);
			if (psd_alarms_do_send_email) {
				psd_debug(5, "Sending email for alarm: %s", description);
				psd_alarms_send_email("OSCARS", description, 0);
			}
		}
	}
	pthread_mutex_unlock(&alarm_table_lock);

	return 0;
}

int psd_alarms_circuit_created_handler(const char *event_id, void *arg, const char *description) {
	pthread_mutex_lock(&alarm_table_lock);
	{
		psdAlarm *alarm;

		alarm = __psd_alarms_lookup_oscars_alarm(arg);
		if (alarm) {
			// Hey, the circuit worked, we can close the alarm
			LIST_REMOVE(alarm, list);
			if (psd_alarms_do_send_email) {
				psd_alarms_send_email("OSCARS", description, 1);
			}
		}
	}
	pthread_mutex_unlock(&alarm_table_lock);

	return 0;
}

int psd_alarms_connection_started_handler(const char *event_id, void *arg, const char *description) {
	pthread_mutex_lock(&alarm_table_lock);
	{

	}
	pthread_mutex_unlock(&alarm_table_lock);

	return 0;
}

int psd_alarms_connection_failed_handler(const char *event_id, void *arg, const char *description) {
	pthread_mutex_lock(&alarm_table_lock);
	{

	}
	pthread_mutex_unlock(&alarm_table_lock);

	return 0;
}
